<?php

namespace plugin\thinkphp\grid;

use think\db\Query;
use think\facade\Db;
use think\model\relation\BelongsTo;
use think\model\relation\BelongsToMany;
use think\model\relation\HasMany;
use think\model\relation\HasOne;
use think\model\relation\MorphMany;
use think\model\relation\MorphOne;


class Filter
{
    /**
     * @var Query
     */
    protected $builder;

    public function __construct(Query $builder, $rule = [])
    {
        $tableField = Db::connect($builder->getModel()->getConnection())->getTableFields($builder->getModel()->getTable());
        $builder->where(function ($query) use ($rule, $tableField) {
            $this->builder = $query;
            foreach ($rule as $item) {
                if (is_numeric($item['value']) || !empty($item['value'])) {
                    $fields = explode('->', $item['field']);
                    $field = current($fields);
                    if ($item['relation'] || in_array($field, $tableField)) {
                        $this->parseFilter($item['type'], $item['relation'], $item['rule'], $item['field'], $item['value']);
                    }
                }
            }
        });
    }

    /**
     * 关联查询
     * @param $relation 关联方法 支持.
     * @param Query $builder
     * @param \Closure $closure 条件闭包
     * @param string $logic AND | OR
     * @return mixed
     */
    public function whereRelation($relation, $builder, \Closure $closure = null, $logic = 'AND')
    {
        $query = $this->whereRelationQuery($relation, $builder, $closure);
        return $builder->whereExists($query->buildSql(), $logic);
    }

    protected function whereRelationQuery($relation, $builder, \Closure $closure)
    {
        list($query, $relation) = $this->has($relation, $builder);
        $this->whereRelationNest($relation, $query, $closure);
        if (!$relation && $closure) {
            call_user_func($closure, $query);
        }
        return $query;
    }

    protected function whereRelationNest($relation, $builder, $closure)
    {
        if ($relation) {
            $query = $this->whereRelationQuery($relation, $builder, $closure);
            return $builder->whereExists($query->buildSql());
        }
    }

    protected function has($relation, $builder)
    {
        $relations = explode('.', $relation);
        $relation = array_shift($relations);
        $method = $builder->getModel()->$relation();
        $query = $method->getQuery();
        $table = $query->getTable();
        if ($method instanceof HasMany || $method instanceof HasOne) {
            $query->whereRaw("{$table}.{$method->getForeignKey()}={$method->getParent()->getTable()}.{$method->getLocalKey()}");
        } else if ($method instanceof BelongsTo) {
            $query->whereRaw("{$table}.{$method->getLocalKey()}={$method->getParent()->getTable()}.{$method->getForeignKey()}");
        } else if ($method instanceof MorphOne || $method instanceof MorphMany) {
            $reflectionClass = new \ReflectionClass($method);
            $propertys = ['morphKey', 'morphType', 'type'];
            $propertyValues = [];
            foreach ($propertys as $var) {
                $property = $reflectionClass->getProperty($var);
                $property->setAccessible(true);
                $propertyValues[] = $property->getValue($method);
            }
            list($morphKey, $morphType, $typeValue) = $propertyValues;
            $query->whereRaw("{$morphKey}={$builder->getTable()}.{$builder->getPk()}")->where($morphType, $typeValue);
        } else if($method instanceof BelongsToMany){
            $query = $method;
        }
        return [$query, implode('.', $relations)];
    }

    protected function whereHas($type, $relation, $rule, $field, $value, $builder = null)
    {
        list($query, $relation) = $this->has($relation, $builder);
        $this->parseFilter($type, $relation, $rule, $field, $value, $query);
        return $query;
    }

    /**
     * 解析筛选
     * @param string $type 类型
     * @param string $relation 关联方法
     * @param string $rule 规则类型
     * @param string $field 字段
     * @param mixed $value 筛选值
     * @param Query $builder
     */
    public function parseFilter($type, $relation, $rule, $field, $value, $builder = null)
    {
        if (is_null($builder)) {
            $builder = $this->builder;
        }
        if ($relation) {
            $query = $this->whereHas($type, $relation, $rule, $field, $value, $builder);
            return $builder->whereExists($query->buildSql());
        }
        if ($type == 'cascader') {
            return $builder->where(function ($query) use ($rule, $value) {
                foreach ($value as $row) {
                    $query->whereOr(function ($q) use ($rule, $row) {
                        foreach ($row as $field => $val) {
                            $this->parseFilter('normal', null, $rule, $field, $val, $q);
                        }
                    });
                }
            });
        }

        switch ($rule) {
            case 'eq':
                $builder->where($field, $value);
                break;
            case 'neq':
                $builder->where($field, '!=', $value);
                break;
            case 'egt':
                $builder->where($field, '>=', $value);
                break;
            case 'elt':
                $builder->where($field, '<=', $value);
                break;
            case 'gt':
                $builder->where($field, '>', $value);
                break;
            case 'lt':
                $builder->where($field, '<', $value);
                break;
            case 'between':
                $builder->whereBetween($field, $value);
                break;
            case 'notBetween':
                $builder->whereNotBetween($field, $value);
                break;
            case 'like':
                $builder->where($field, 'LIKE', "%$value%");
                break;
            case 'json':
                list($field, $node) = explode('->', $field);
                $builder->whereRaw("JSON_EXTRACT({$field},'$.{$node}') = '{$value}'");
                break;
            case 'jsonLike':
                list($field, $node) = explode('->', $field);
                $builder->whereRaw("JSON_EXTRACT({$field},'$.{$node}') LIKE '%{$value}%'");
                break;
            case 'jsonArrLike':
                list($field, $node) = explode('->', $field);
                $builder->whereRaw("JSON_EXTRACT({$field},'$[*].{$node}') LIKE '%{$value}%'");
                break;
            case 'in':
                $builder->whereIn($field, $value);
                break;
            case 'notIn':
                $builder->whereNotIn($field, $value);
                break;
            case 'findIn':
                $builder->whereRaw("FIND_IN_SET('{$value}',{$field})");
                break;
        }
    }
}
